/**
 * Copyright (c) 2009 Cerner Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Cerner Corporation - initial API and implementation
 */
package com.cerner.system.rest.resource.organizer;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import com.cerner.system.rest.internal.arguments.NotNulls;
import com.cerner.system.rest.resource.organizer.spi.AbstractResourceController;
import com.cerner.system.rest.resource.organizer.spi.NotThreadSafe;
import com.cerner.system.rest.resource.organizer.spi.ResourceKey;
import com.cerner.system.rest.resource.organizer.spi.ResourceRef;

/**
 * Implementation of a {@link ResourceController} that uses a map data structure
 * to store resources in a hierarchy.
 * 
 * @author Alex Horn
 */
@NotThreadSafe
public class MapResourceController<E> extends AbstractResourceController<E> {

  /**
   * Immutable value object that uniquely identifies the resource for which this
   * controller is responsible.
   */
  private final ResourceKey key;

  /**
   * Internal API that provides read and write access to the underlying map data
   * structure.
   */
  private final MapResourceOrganizer<E> organizer;

  /**
   * Instantiate a {@link MapResourceController} through which consumers can
   * navigate, access and modify the representation of a resource in a
   * hierarchical tree structure.
   * 
   * @param key uniform identifier for the resource that this controller
   *          represents
   * @param organizer internal API through which the underlying data structure
   *          is accessed and modified
   */
  protected MapResourceController(final ResourceKey key,
      final MapResourceOrganizer<E> organizer) {
    this.key = key;
    this.organizer = organizer;
  }

  public java.net.URI uri() {
    return this.key.uri();
  }

  public MapResourceController<E> parent() {
    final ResourceKey parentKey = this.key.parent();
    if (parentKey == null) {
      return null;
    } else {
      return new MapResourceController<E>(parentKey, this.organizer);
    }
  }

  public E get() {
    final ResourceRef<E> node = ref();
    if (node != null) {
      return node.get();
    } else {
      return null;
    }
  }

  /*
   * MapResourceController does not maintain state by itself. It uses a mutable
   * MapResourceRef object to record the state of a node in the tree data
   * structure.
   */
  public E put(final E representation) {
    NotNulls.names("representation").values(representation);

    buildTree();
    return ref().set(representation);
  }

  public Set<java.net.URI> delete() {
    final MapResourceRef<E> node = ref();
    if (node != null) {
      final Collection<ResourceKey> keys = node.descendants();
      this.organizer.removeAll(keys);

      final Set<java.net.URI> deletedUris = toUris(keys);
      return Collections.unmodifiableSet(deletedUris);
    } else {
      return Collections.emptySet();
    }
  }

  /**
   * Internal API that creates or updates the ancestors of the resource node
   * that corresponds to the {@link ResourceKey} which has been injected as part
   * of the constructor. Once the method has executed the underlying map data
   * structure must maintain enough information so that {@link #delete()}
   * operation can meet the specification.
   * 
   * @see MapResourceController#delete()
   */
  protected void buildTree() {

    /* loop variants */
    final Set<ResourceKey> descendents = new HashSet<ResourceKey>(
        this.key.level());
    MapResourceController<E> ancestor;
    MapResourceRef<E> ancestorRef;

    for (ancestor = this; ancestor != null; ancestor = ancestor.parent()) {
      descendents.add(ancestor.key);

      ancestorRef = ancestor.ref();
      if (ancestorRef == null) {
        ancestorRef = new MapResourceRef<E>();
        this.organizer.put(ancestor.key, ancestorRef);
      }

      ancestorRef.update(descendents);
    }
  }

  /**
   * Internal API to find the {@code MapResourceRef} that is associated with
   * this resource controller.
   * 
   * @return resource reference stored with the key of this controller or
   *         {@code null} if none could be found
   */
  protected final MapResourceRef<E> ref() {
    return this.organizer.get(this.key);
  }

  /**
   * Convert a collection of {@link ResourceKey keys} to their corresponding set
   * of URIs.
   * 
   * @return modifiable collection of URIs
   */
  private static Set<java.net.URI> toUris(final Collection<ResourceKey> keys) {
    final Set<java.net.URI> uris = new HashSet<java.net.URI>(keys.size());
    for (ResourceKey key : keys) {
      uris.add(key.uri());
    }
    return uris;
  }

}
